Skip to content

Linux 守护进程:进程组、会话与创建步骤 ​

守护进程(daemon)是脱离控制终端、在后台长期运行的进程——服务器上的 sshd、nginx 都是。创建守护进程只有几步代码,但要理解为什么这么写,得先讲清三个基础概念:终端、进程组、会话。本文从概念到 setsid 的每一步给出动机,读完能独立写出不出错的守护进程初始化代码。

终端、进程组、会话 ​

  • 进程组:一个或多个进程的集合,用进程组 ID(PGID)标识。组长进程的 PID 等于 PGID;组长退出,进程组仍在。kill 的参数既可以发进程也可以发整组
  • 会话:一个或多个进程组的集合,通常从用户登录建立、到用户退出结束,期间该用户启动的进程都属此会话。建立会话的进程是会话首进程
  • 控制终端:与会话关联的终端设备,信息记录在 PCB 中。一个控制终端对应一个会话;会话里唯一的前台进程组能从终端读输入,其余是后台进程组

登录 shell 下的典型布局:

管道 find | sort | wc 里的三个命令恰好构成一个进程组;vim 正在前台,所以键盘输入归它。

相关函数 ​

函数原型 说明
pid_t getpgrp(void); 获取当前进程组 id
pid_t getpgid(pid_t pid); 获取指定进程的组 id
int setpgid(pid_t pid, pid_t pgid); 设置指定进程的组 id
pid_t getsid(pid_t pid); 获取指定进程的会话 id
pid_t setsid(void); 创建新会话并成为会话首进程

创建守护进程的六个步骤 ​

c
pid_t pid = fork();
if (pid > 0) exit(0);        // 1. 父进程退出
setsid();                    // 2. 子进程创建新会话
umask(0);                    // 3. 清除文件权限掩码
chdir("/");                  // 4. 切换工作目录到根目录
// 5. 关闭继承的文件描述符
// 6. 将 0、1、2 重定向到 /dev/null

每一步的动机:

1. fork 后父进程退出。 目的有二:一是让 shell 认为命令已执行完,回到提示符(形式上脱离控制终端);二是保证接下来的子进程不是进程组组长——组长调用 setsid 会失败,而父进程直接 fork 出来的子进程的 PGID 继承自父,不可能等于自己的 PID。子进程随后成为孤儿进程,由 1 号进程收养。

2. setsid() 创建新会话。 这是核心一步,调用进程成为新会话的首进程和新进程组的组长,一举摆脱原会话、原进程组、原控制终端。没有这一步,终端关闭时进程会收到挂断信号(SIGHUP)被带走。

3. umask(0) 清权限掩码。 子进程继承了父进程的文件权限掩码(如 050 会屏蔽组用户的读与执行位),不清零的话守护进程后续创建文件的权限将不可预期。清零后由创建调用(open/mkdir)显式指定权限。

4. chdir("/") 切换工作目录。 子进程继承父进程的工作目录,而工作目录所在的文件系统在进程占用期间无法卸载——守护进程长期活着,等于把某个挂载点钉死了。切到根目录(或 /tmp 这类永远在的路径)解除占用。

5. 关闭继承的文件描述符。 从父进程继承的打开文件不但占资源,同样会钉住文件系统。守护进程已脱离终端,标准输入、输出、错误(fd 0、1、2)没有存在的意义,一并关闭。

6. 重定向 0、1、2 到 /dev/null。 直接关闭这三个 fd 有隐患:守护进程之后调用的库或第三方代码一旦习惯性往 stderr 写日志、从 stdin 读输入,会错误地读写到后来打开的其他文件(fd 会复用最小的空闲编号)。打开 /dev/null 并 dup2 到 0、1、2 上,读写永远无害:

c
int fd = open("/dev/null", O_RDWR);
dup2(fd, STDIN_FILENO);
dup2(fd, STDOUT_FILENO);
dup2(fd, STDERR_FILENO);
if (fd > STDERR_FILENO) close(fd);

现代开发中通常不手写这些步骤,而是调用 BSD 的 daemon(1, 1) 或 systemd 等服务管理器接管;理解每步的动机仍有价值——排查「终端一关服务就死」「文件系统卸载不掉」这类问题时,答案就藏在这六步里。

要点 ​

  • 前台进程组独占控制终端输入;会话首进程建立会话,一个终端一个会话
  • 六步各自的动机:fork+退出保证子进程不是组长(setsid 的前提);setsid 摆脱终端(防 SIGHUP);umask 保证文件权限可控;chdir 解除挂载点占用;关 fd 省资源;重定向 /dev/null 防 fd 复用误伤
  • 关闭 0/1/2 后必须重定向,不能裸关
  • 实际项目优先用 daemon() 或 systemd,但排障时这六步是检查清单

相关文章 ​

最近更新

基于 VitePress 构建